
Imports System.IO
Imports System.Net.Sockets

Namespace tcpTrans
    Public Module tcpTrans
        Private TCP_SEND_BYTES_MS As Int64
        Private TCP_RECEIVE_BYTES_MS As Int64
        Public ReadOnly dataSizePrefixLength As Int32 = sizeof(Of Int64)()
        Public Const defaultSleepTime As Int32 = 1
        Private Const checksum_8_64 As Int64 = checksum_8
        Private ReadOnly leadCodeLeftShift As Byte = ((dataSizePrefixLength - sizeof(Of Int32)()) << bitshiftInByte)

        Private Function size_code(ByVal size As Int32) As Int64
            Dim i64 As Int64 = 0
            i64 = size
            Return (checksum_8_64 << 56) + ((i64 And maxUInt16) << 40) + (i64 << 8) + checksum_8
        End Function

        Private Function size(ByVal size_code As Int64) As Int32
            Return (size_code >> 8) And maxInt32
        End Function

        Public Function initial_send(ByVal tcpClient As TcpClient, ByRef stream As NetworkStream,
                                     ByVal holdtime As Int64, ByVal data As nettransData,
                                     ByRef buff() As Byte, ByRef size As Int64,
                                     ByVal legacyDataSize As Boolean) As ternary
            If clientClosed(tcpClient) Then
                If isdebugmode() Then
                    raiseError(errorHandle.displayMode.console, "client is not connected to " +
                               remoteEndPoint(tcpClient), errorHandle.errorType.exclamation)
                End If
                Return ternary.false
            End If
            If data Is Nothing Then
                raiseError(errorHandle.displayMode.console, "do not get valid nettransData.",
                           errorHandle.errorType.exclamation)
                Return ternary.false
            End If

            Dim tmp As bytes = Nothing
            Dim size2 As Int64 = 0
            If data.export(buff, dataSizePrefixLength, size) AndAlso
               size > 0 AndAlso
               size <= maxInt32 AndAlso
               ((legacyDataSize AndAlso eva(size2, size)) OrElse
               (Not legacyDataSize AndAlso eva(size2, size_code(size)))) AndAlso
               convertor.tobytes(size2, tmp) Then
                assert(tmp.size() = dataSizePrefixLength)
                memcpy(buff, 0, +tmp, 0, dataSizePrefixLength)
                size += dataSizePrefixLength
                stream = tcpClient.GetStream()
                If stream Is Nothing Then
                    Return ternary.false
                Else
                    stream.WriteTimeout() = ifoper(holdtime <= 0, 1, holdtime)
                    Return ternary.unknown
                End If
            Else
                raiseError("cannot convert size to bytes, size " + Convert.ToString(size),
                           errorHandle.errorType.exclamation)
                Return ternary.false
            End If
        End Function

        Public Function initial_receive(ByVal tcpClient As TcpClient,
                                        ByRef stream As NetworkStream,
                                        ByVal holdtime As Int64,
                                        ByRef data As nettransData,
                                        ByVal needOvertimeOrNotConnectedInfo As Boolean,
                                        ByVal needClearData As Boolean) As ternary
            If clientClosed(tcpClient) Then
                If needOvertimeOrNotConnectedInfo Then
                    raiseError(errorHandle.displayMode.console, "client is not connected to " + _
                                remoteEndPoint(tcpClient), errorHandle.errorType.exclamation)
                End If
                Return ternary.false
            End If
            If data Is Nothing Then
                data = New nettransData()
            ElseIf needClearData Then
                data.clear()
            End If

            stream = tcpClient.GetStream()
            If stream Is Nothing Then
                Return ternary.false
            Else
                stream.ReadTimeout() = ifoper(holdtime <= 0, 1, holdtime)
                Return ternary.unknown
            End If
        End Function

        Public Function dataLength(ByVal buff As bytes, ByRef len As Int64,
                                   ByVal legacyDataSize As Boolean) As Boolean
            Return convertor.toint64(buff, len) AndAlso
                   (legacyDataSize AndAlso (len >> leadCodeLeftShift) = 0) OrElse
                   (size_code(size(len)) = len AndAlso
                    eva(len, size(len))) AndAlso
                   len > 0
        End Function

        Public Sub initialTcpClient(ByVal client As TcpClient)
            assert(Not client Is Nothing)
            socketTrans.initial(client.Client)
            client.NoDelay() = True
            client.SendBufferSize() = 0
        End Sub

        Public Sub closeTcpClient(ByRef client As TcpClient)
            If Not client Is Nothing Then
                On Error Resume Next
#If PocketPC OrElse Smartphone Then
                If client.Client.Connected Then
#Else
                If client.Connected OrElse client.Client.Connected Then
#End If
                    closestream(client.GetStream())
                    socketTrans.close(client.Client)
                End If
                client.Close()
                client = Nothing
            End If
        End Sub

        Public Function clientClosed(ByVal client As TcpClient) As Boolean
            Dim rtn As Boolean = False
#If PocketPC OrElse Smartphone Then
            rtn = client Is Nothing OrElse client.Client Is Nothing OrElse Not client.Client.Connected
#Else
            rtn = client Is Nothing OrElse client.Client Is Nothing OrElse _
                  Not client.Connected OrElse Not client.Client.Connected
#End If

#If CANNOTDETERMINE Then
            Dim rtn As Boolean = False
            rtn = client Is Nothing OrElse client.Client Is Nothing

            If Not rtn Then
                Try
                    'peek first
                    Dim timeout As Int32
                    Dim block As Boolean
                    timeout = client.Client.ReceiveTimeout()
                    block = client.Client.Blocking()
                    client.Client.ReceiveTimeout = 10
                    client.Client.Blocking = False
                    Static b(0) As Byte
                    client.Client.Receive(b, 1, SocketFlags.Peek)
                    client.Client.ReceiveTimeout() = timeout
                    client.Client.Blocking = block
                Catch
                End Try

                rtn = client Is Nothing OrElse client.Client Is Nothing OrElse _
                      Not client.Connected OrElse Not client.Client.Connected
            End If
#End If

            If Not rtn Then
                Dim err As SocketError = SocketError.Success
                Try
                    Static b(-1) As Byte
                    If client.Client.Send(b, 0, 0, SocketFlags.None, err) <> 0 OrElse
                       (err <> SocketError.Success AndAlso err <> SocketError.WouldBlock) Then
                        Return True
                    Else
                        Return False
                    End If
                Catch
                    Return True
                End Try
            End If

            Return rtn
        End Function

        Public Function safe_div_duration(ByVal startms As Int64) As Int64
            Dim timems As Int64
            timems = ticksToMilliseconds(Now.Ticks) - startms
            Return ifoper(timems = 0, 1, timems)
        End Function

        Public Function readyToReceive(ByVal client As TcpClient) As Boolean
            Return Not tcpTrans.clientClosed(client) AndAlso client.Available >= dataSizePrefixLength
        End Function

        Public Function send(ByVal tcpClient As TcpClient, ByVal data As nettransData,
                             ByVal holdTime As Int64, Optional ByVal legacyDataSize As Boolean = False) As Boolean
            Dim buff() As Byte = Nothing
            Dim init As ternary = Nothing
            Dim size As Int64
            Dim stream As NetworkStream = Nothing
            init = initial_send(tcpClient, stream, holdTime, data, buff, size, legacyDataSize)
            If init.notunknown() Then
                Return init.true_()
            End If

            Dim startTime As Int64 = ticksToMilliseconds(Now.ticks)
            Dim rtn As Boolean
            If writestream(stream, buff, lengthInBuff:=size,
                           starttime:=startTime, holdtime:=holdTime, sleeptime:=defaultSleepTime) Then
                counter.increase(TCP_SEND_BYTES_MS, (size + dataSizePrefixLength) \ safe_div_duration(startTime))
                rtn = True
            Else
                raiseError(errorHandle.displayMode.console, "failed to send data to " +
                           remoteEndPoint(tcpClient), errorHandle.errorType.exclamation)
                rtn = False
            End If

            [erase](buff)
            Return rtn
        End Function

        Public Function receive(ByVal tcpClient As TcpClient, ByRef data As nettransData, _
                                ByVal holdTime As Int64, _
                                Optional ByVal needOvertimeOrNotConnectedInfo As Boolean = True, _
                                Optional ByVal needClearData As Boolean = True,
                                Optional ByVal legacyDataSize As Boolean = False) As Boolean
            Dim init As ternary = Nothing
            Dim stream As NetworkStream = Nothing
            init = initial_receive(tcpClient, stream, holdTime, data, needOvertimeOrNotConnectedInfo, needClearData)
            If init.notunknown() Then
                Return init.true_()
            End If

            Dim startTime As Int64 = ticksToMilliseconds(Now.ticks)
            Dim buff As bytes = Nothing
            Dim dataLength As Int64

            While Not stream Is Nothing AndAlso Not stream.DataAvailable _
                  AndAlso ticksToMilliseconds(Now.ticks) - startTime < holdTime
                sleep(defaultSleepTime)
            End While

            Dim rtn As Boolean = False
            If ticksToMilliseconds(Now.ticks) - startTime < holdTime Then
                If readstream(stream, buff, , dataSizePrefixLength, , _
                              startTime, holdTime, defaultSleepTime) = dataSizePrefixLength Then
                    If Not tcpTrans.dataLength(buff, dataLength, legacyDataSize) Then
                        raiseError(errorHandle.displayMode.console, _
                                   "cannot get head information, skip receiving from " + _
                                    remoteEndPoint(tcpClient), errorHandle.errorType.exclamation)
                        rtn = False
                    Else
                        If readstream(stream, buff, , dataLength, , , , defaultSleepTime) = dataLength Then
                            counter.increase(TCP_RECEIVE_BYTES_MS,
                                             (dataLength + dataSizePrefixLength) \ safe_div_duration(startTime))
                            data.import(buff, 0, dataLength)
                            rtn = True
                        Else
                            raiseError(errorHandle.displayMode.console, "failed to receive data from " + _
                                       remoteEndPoint(tcpClient), errorHandle.errorType.exclamation)
                            rtn = False
                        End If
                    End If
                Else
                    raiseError(errorHandle.displayMode.console, _
                               "failed to receive head information, skip receiving from " + _
                                remoteEndPoint(tcpClient), errorHandle.errorType.exclamation)
                    rtn = False
                End If
            Else
                If needOvertimeOrNotConnectedInfo Then
                    raiseError(errorHandle.displayMode.console, "receiving timeout, skip receive from " + _
                                remoteEndPoint(tcpClient), errorHandle.errorType.exclamation)
                End If
                rtn = False
            End If
            Return rtn
        End Function

        Sub New()
            TCP_RECEIVE_BYTES_MS = counter.registerAverageAndLastAverage("TCP_RECEIVE_BYTES_MS")
            TCP_SEND_BYTES_MS = counter.registerAverageAndLastAverage("TCP_SEND_BYTES_MS")
        End Sub
    End Module
End Namespace
